﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;

namespace Dycox.Linq.Data
{
    public sealed class EntityKey : Collection<EntityKeyItem>
    {
        public EntityKey()
        {

        }

        private void AddingItem(EntityKeyItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");

            if (ContainsMember(item.MemberName))
                throw new InvalidOperationException(SR.GetString("name_existed"));
        }

        protected override void InsertItem(int index, EntityKeyItem item)
        {
            AddingItem(item);
            base.InsertItem(index, item);
        }

        protected override void SetItem(int index, EntityKeyItem item)
        {
            AddingItem(item);
            base.SetItem(index, item);
        }

        private EntityKeyItem GetItem(string name)
        {
            return this.SingleOrDefault(o => o.MemberName == name);
        }

        public bool ContainsMember(string name)
        {
            return GetItem(name) != null;
        }

        public object this[string name]
        {
            get
            {
                var item = GetItem(name);
                if (item != null)
                    return item.Value;
                else
                    return null;
            }
            set
            {
                var item = GetItem(name);
                if (item != null)
                    item.Value = value;
                else
                    Add(new EntityKeyItem(name, value));
            }
        }

        // override object.Equals
        public override bool Equals(object obj)
        {
            EntityKey other = obj as EntityKey;

            if (other == null)
                return false;

            if (object.ReferenceEquals(obj, this))
                return true;

            if (this.Count != other.Count)
                return false;

            foreach (var item0 in this)
            {
                var item1 = other.GetItem(item0.MemberName);
                if (item1 == null)
                    return false;

                if (!item0.Value.Equals(item1.Value))
                    return false;
            }

            return true;
        }

        // override object.GetHashCode
        public override int GetHashCode()
        {
            int hash = 0;

            foreach (var item in this)
            {
                hash ^= item.MemberName.GetHashCode();
                hash ^= item.Value.GetHashCode();
            }

            return hash;
        }

        public LambdaExpression CreateExpression(Type entityType)
        {
            Expression expr = null;
            ParameterExpression pexpr = Expression.Parameter(entityType);

            foreach (var item in this)
            {
                MemberExpression mexpr = Expression.PropertyOrField(pexpr, item.MemberName);

                Type type;

                if (mexpr.Member is PropertyInfo)
                    type = ((PropertyInfo)mexpr.Member).PropertyType;
                else
                    type = ((FieldInfo)mexpr.Member).FieldType;

                object value = item.Value;

                if (value.GetType() != type)
                {
                    value = Convert.ChangeType(value, type);
                }

                BinaryExpression eqexpr =
                    Expression.Equal(
                    Expression.PropertyOrField(pexpr, item.MemberName),
                    Expression.Constant(value, type));

                if (expr == null) expr = eqexpr;
                else expr = Expression.And(expr, eqexpr);

            }

            if (expr == null)
                throw new InvalidOperationException(SR.GetString("collection_contains_no_item"));

            return Expression.Lambda(expr, pexpr);
        }

        static MethodInfo SingleOrDefaultMethod;

        static MethodInfo GetSingleOrDefaultMethod(Type type)
        {
            if (SingleOrDefaultMethod == null)
            {
                SingleOrDefaultMethod = (System.Reflection.MethodInfo)typeof(Queryable).FindMembers(System.Reflection.MemberTypes.Method, System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static,
                    (m, c) =>
                    {
                        if (m.Name == (string)c)
                        {
                            if (((System.Reflection.MethodInfo)m).GetParameters().Length == 2)
                                return true;
                        }

                        return false;
                    }, "SingleOrDefault")[0];
            }

            return SingleOrDefaultMethod.MakeGenericMethod(type);
        }

        public object Execute(IQueryable query)
        {
            Expression expr = CreateExpression(query.ElementType);

            expr = Expression.Call(null, GetSingleOrDefaultMethod(query.ElementType),
                Expression.Constant(query), expr);

            return query.Provider.Execute(expr);
        }

        public T Execute<T>(IQueryable<T> query)
        {
            return (T)this.Execute((IQueryable)query);
        }

    }

    [DataContract, DebuggerDisplay("{MemberName} = {Value}")]
    public class EntityKeyItem
    {
        public EntityKeyItem(string memberName, object value)
        {
            if (memberName == null)
                throw new ArgumentNullException("memberName");

            _memberName = memberName;
            Value = value;
        }

        [Newtonsoft.Json.JsonConstructor]
        private EntityKeyItem(string memberName, object value, string valueType)
            : this(memberName, value)
        {
            if (!object.Equals(valueType, _valueType))
            {
                if (valueType == null)
                    _value = Dycox.Utils.ConvertUtil.ConvertType<string>(value);
                else
                    _value = Dycox.Utils.ConvertUtil.ConvertType(value, Type.GetType(valueType, true));
                _valueType = valueType;
            }
        }

        [DataMember(Name = "memberName")]
        private string _memberName;

        public string MemberName
        {
            get { return _memberName; }
        }

        [DataMember(Name = "value")]
        private object _value;

        public object Value
        {
            get { return _value; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                _value = value;

                Type type = value.GetType();
                if (type != typeof(string))
                    _valueType = type.FullName;
                else
                    _valueType = null;
            }
        }

        [DataMember(Name = "valueType", EmitDefaultValue = false), DefaultValue(null)]
        private string _valueType;

    }
}
